package com.gitee.dolt.security.token;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.gitee.dolt.core.constant.RoleConstant;
import com.gitee.dolt.core.context.ContextConstants;
import com.gitee.dolt.core.context.ContextUtil;
import com.gitee.dolt.core.exception.BizException;
import com.gitee.dolt.core.exception.code.ExceptionCode;
import com.gitee.dolt.security.constant.SecurityConstants;
import com.gitee.dolt.security.model.AuthInfo;
import com.gitee.dolt.security.model.SysToken;
import com.gitee.dolt.security.model.SysUser;
import com.gitee.dolt.security.properties.SecurityProperties;
import com.gitee.dolt.utils.EnvUtils;
import com.gitee.dolt.utils.StrUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;


/**
 * 网关：
 * 获取token，并解析，然后将所有的用户、应用信息封装到请求头
 * <p>
 * 拦截器：
 * 解析请求头数据， 将用户信息、应用信息封装到BaseContextHandler
 * 考虑请求来源是否网关（ip等）
 * <p>
 *
 * @author LYH
 */
@Slf4j
@RequiredArgsConstructor
public class TokenHandlerInterceptor extends HandlerInterceptorAdapter {

    private final String profiles;
    private final TokenProvider tokenProvider;
    private final List<TokenHandlerIntervention> tokenHandlerInterventions;
    private final SecurityProperties securityProperties;
    private final AntPathMatcher pathMatcher = new AntPathMatcher();
    private final static String TEST_TOKEN = "test";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            log.info("not exec!!! url={}", request.getRequestURL());
            return super.preHandle(request, response, handler);
        }

        String uri = request.getRequestURI();
        String method = request.getMethod();
        List<String> disableApis = new ArrayList<>();
        List<String> ignores = new ArrayList<>();
        for (TokenHandlerIntervention tokenHandlerIntervention : getTokenHandlerInterventions()) {
            disableApis.addAll(tokenHandlerIntervention.disableApis());
            ignores.addAll(tokenHandlerIntervention.ignores());
        }
        // 禁用的api
        for (String disabledApi : disableApis) {
            if (uriMatch(uri, method, disabledApi)) {
                throw new BizException(ExceptionCode.DISABLED_API);
            }
        }

        // 忽略 token 认证
        for (String ignore : ignores) {
            if (uriMatch(uri, method, ignore)) {
                return super.preHandle(request, response, handler);
            }
        }

        // 解析token
        parseToken(request, response);

        return super.preHandle(request, response, handler);
    }

    private void parseToken(HttpServletRequest request, HttpServletResponse response) {
        //获取token， 解析，然后想信息放入 header
        //获取token
        String token = getToken(request);
        String noAuthToken = getValue(ContextConstants.NO_AUTH_TOKEN_HEADER, request);
        Boolean takeOver = Convert.toBool(getValue(ContextConstants.NO_AUTH_TOKEN_TAKE_OVER_HEADER, request));
        AuthInfo authInfo;
        SecurityProperties.JwtProperties jwtProperties = securityProperties.getJwt();
        if (isDev(token)) {
            SysUser sysUser = new SysUser().setId(1L).setAccount("TEST").setName("TEST").setScope("ALL")
                    .setRoles(Arrays.asList(RoleConstant.SUPER_ADMIN)).setResources(Arrays.asList("*"));
            SysToken sysToken = new SysToken().setTokenType(ContextConstants.BEARER_HEADER_KEY).setToken(token);
            authInfo = new AuthInfo().setUser(sysUser).setToken(sysToken);
        } else {
            try {
                authInfo = tokenProvider.getAuthInfo(token);
            } catch (Exception e) {
                if (BooleanUtil.isTrue(takeOver) && noAuthToken.equals(jwtProperties.getNoAuthToken())) {
                    SysUser sysUser = new SysUser().setId(2L).setAccount("NO_AUTH_TAKE_OVER").setName("NO_AUTH_TAKE_OVER").setScope("ALL")
                            .setRoles(Arrays.asList(RoleConstant.SUPER_ADMIN)).setResources(Arrays.asList("*"));
                    SysToken sysToken = new SysToken().setTokenType(ContextConstants.BEARER_HEADER_KEY).setToken(token);
                    authInfo = new AuthInfo().setUser(sysUser).setToken(sysToken);
                } else {
                    throw e;
                }
            }

        }

        // 转换，将 token 解析出来的用户身份 和 解码后的Authorization 重新封装到请求头
        SysUser user = authInfo.getUser();
        ContextUtil.setUserId(user.getId());
        ContextUtil.setAccount(user.getAccount());
        ContextUtil.setName(user.getName());
        ContextUtil.setScope(user.getScope());
        ContextUtil.setRoles(user.getRoles());
        ContextUtil.setResources(user.getResources());
        ContextUtil.setToken(token);

        if (StrUtil.isNotEmpty(noAuthToken) && noAuthToken.equals(jwtProperties.getNoAuthToken())) {
            user.setResources(Arrays.asList("*"));
        }

        user.forEach((k, v) -> {
            if (v != null && ContextUtil.get(k) == null) {
                ContextUtil.set(k, v);
            }
        });

        String uri = request.getRequestURI();
        String method = request.getMethod();
        boolean setNoAuthKey = false;
        for (TokenHandlerIntervention tokenHandlerIntervention : getTokenHandlerInterventions()) {
            tokenHandlerIntervention.afterParse(token, authInfo, request, response);
            if (!setNoAuthKey) {
                for (String noAuth : tokenHandlerIntervention.noAuths()) {
                    if (uriMatch(uri, method, noAuth)) {
                        // 这里主要是为了权限验证那里使用
                        request.setAttribute(SecurityConstants.NO_AUTH_KEY, true);
                        setNoAuthKey = true;
                    }
                }
            }

        }
    }

    private boolean uriMatch(String uri, String method, String value) {
        String[] s = value.split("[,|]");
        if (pathMatcher.match(s[0], uri)) {
            if (s.length > 1) {
                for (int i = 1; i < s.length; i++) {
                    if (s[i].equals(StrUtils.ASTERISK) || s[i].equalsIgnoreCase(method)) {
                        return true;
                    }
                }
            } else {
                return true;
            }
        }
        return false;
    }

    private boolean isDev(String token) {
        String profiles = StrUtil.trim(this.profiles);
        return (EnvUtils.DEV.equalsIgnoreCase(profiles)
                || EnvUtils.TEST.equalsIgnoreCase(profiles))
                && TEST_TOKEN.equalsIgnoreCase(token);
    }

    private String getToken(HttpServletRequest request) {
        String value = getValue(ContextConstants.BEARER_HEADER_KEY, request);
        if (value == null) {
            return null;
        }
        if (value.startsWith(ContextConstants.BEARER_HEADER_PREFIX)) {
            value = value.substring(ContextConstants.BEARER_HEADER_PREFIX.length());
        }
        return URLUtil.decode(value);
    }

    private String getValue(String name, HttpServletRequest request) {
        String value = request.getParameter(name);
        if (StrUtil.isEmpty(value)) {
            value = request.getHeader(name);
        }
        return value;
    }

    public List<TokenHandlerIntervention> getTokenHandlerInterventions() {
        if (tokenHandlerInterventions == null) {
            return Collections.emptyList();
        }
        return tokenHandlerInterventions;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ContextUtil.remove();
        super.afterCompletion(request, response, handler, ex);
    }

}
